﻿Imports System.ComponentModel
Imports System.Drawing.Design
Imports System.ComponentModel.Design
Imports System.Runtime.InteropServices
Imports System.Collections.ObjectModel


<ToolboxBitmap(GetType(System.Windows.Forms.TabControl)), _
Designer(GetType(cTabControlDesigner))> _
Public Class cTabControl
    Inherits System.Windows.Forms.TabControl

    Public Event SelectedIndexChanging As cTabControlEventHandler
    Public HotTab As TabPage = Nothing

    Public Event TabOrderChanged() 'Not part of original Control

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

        ' Allow drop to enable tab-dragging
        Me.AllowDrop = True
    End Sub

    <Editor(GetType(cTabpageCollectionEditor), GetType(UITypeEditor))> _
    Public Shadows ReadOnly Property TabPages() As TabPageCollection
        Get
            Return MyBase.TabPages
        End Get
    End Property

    Friend Class cTabpageCollectionEditor
        Inherits CollectionEditor

        Public Sub New(ByVal type As System.Type)
            MyBase.new(type)
        End Sub

        Protected Overrides Function CreateCollectionItemType() As System.Type
            Return GetType(cTabPage)
        End Function

    End Class



    <StructLayout(LayoutKind.Sequential)> _
    Private Structure NMHDR
        Public HWND As Int32
        Public idFrom As Int32
        Public code As Int32
        Public Overloads Function ToString() As String
            Return String.Format("Hwnd: {0}, ControlID: {1}, Code: {2}", HWND, idFrom, code)
        End Function
    End Structure

    Private Const TCN_FIRST As Int32 = &HFFFFFFFFFFFFFDDA&
    Private Const TCN_SELCHANGING As Int32 = (TCN_FIRST - 2)

    Private Const WM_USER As Int32 = &H400&
    Private Const WM_NOTIFY As Int32 = &H4E&
    Private Const WM_REFLECT As Int32 = WM_USER + &H1C00&


    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
        If m.Msg = (WM_REFLECT + WM_NOTIFY) Then
            Dim hdr As NMHDR = DirectCast(Marshal.PtrToStructure(m.LParam, GetType(NMHDR)), NMHDR)
            If hdr.code = TCN_SELCHANGING Then
                If Not HotTab Is Nothing Then
                    Dim e As New cTabControlEventArgs(HotTab, Me.Controls.IndexOf(HotTab))
                    RaiseEvent SelectedIndexChanging(Me, e)
                    If e.Cancel OrElse HotTab.Enabled = False Then
                        m.Result = New IntPtr(1)
                        Return
                    End If
                End If
            End If
        End If
        MyBase.WndProc(m)
    End Sub

    Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
        MyBase.OnMouseMove(e)
        HotTab = TestTab(New Point(e.X, e.Y))
    End Sub

    Public Sub InsertTabPage(ByVal [tabpage] As TabPage, ByVal [index] As Integer)

        If [index] < 0 Or [index] > TabCount Then
            Throw New ArgumentException("Index out of Range.")
        End If

        TabPages.Add([tabpage])
        If [index] < TabCount - 1 Then
            Do
                SwapTabPages([tabpage], (TabPages(TabPages.IndexOf([tabpage]) - 1)))
            Loop Until TabPages.IndexOf([tabpage]) = [index]
        End If

        SelectedTab = [tabpage]

    End Sub

    Public Sub SwapTabPages(ByVal tp1 As TabPage, ByVal tp2 As TabPage)
        If TabPages.Contains(tp1) = False Or TabPages.Contains(tp2) = False Then
            Throw New ArgumentException("TabPages must be in the TabCotrols TabPageCollection.")
        End If
        Dim Index1 As Integer = TabPages.IndexOf(tp1)
        Dim Index2 As Integer = TabPages.IndexOf(tp2)
        TabPages(Index1) = tp2
        TabPages(Index2) = tp1
    End Sub

    Private Function TestTab(ByVal pt As Point) As TabPage
        For index As Integer = 0 To TabCount - 1
            If GetTabRect(index).Contains(pt.X, pt.Y) Then
                Return TabPages(index)
            End If
        Next
        Return Nothing
    End Function

    Protected Overrides Sub OnDragOver(ByVal drgevent As System.Windows.Forms.DragEventArgs)
        MyBase.OnDragOver(drgevent)

        'Get the coords of our mouse in Client coordinates
        Dim pt As Point = PointToClient(New Point(drgevent.X, drgevent.Y))

        'Get the Tab
        Dim hoverTab As cTabPage = GetTabPageFromPoint(pt)

        If hoverTab IsNot Nothing Then

            If drgevent.Data.GetDataPresent(GetType(cTabPage)) Then

                drgevent.Effect = DragDropEffects.Move

                'Get the dragged tab
                Dim dragTab As cTabPage = DirectCast(drgevent.Data.GetData(GetType(cTabPage)), cTabPage)

                'Get the tabindex of the dragged tab and the hovered tab
                Dim dragIndex As Integer = FindIndex(dragTab)
                Dim dropIndex As Integer = FindIndex(hoverTab)

                'If they are the same we don't need to do anything
                If dragIndex <> dropIndex Then

                    'Add each tabpage, except the dragged tab (!), to a new tabpage collection
                    Dim pages As New Collection(Of cTabPage)
                    For i As Integer = 0 To Me.TabPages.Count - 1

                        If i <> dragIndex Then
                            pages.Add(CType(Me.TabPages(i), cTabPage))
                        End If

                    Next

                    'We now have a collection of all our tabs except the dragged tab
                    'So insert the dragged tab at the appropriate index
                    pages.Insert(dropIndex, dragTab)

                    'Clear and re-add the pages
                    TabPages.Clear()
                    TabPages.AddRange(pages.ToArray())

                    'Select the dragged tab
                    Me.SelectedTab = dragTab
                End If

            Else
                drgevent.Effect = DragDropEffects.None
            End If

        End If
    End Sub

    Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
        MyBase.OnMouseDown(e)

        If e.Button = Windows.Forms.MouseButtons.Left Then
            Dim pt As Point = New Point(e.X, e.Y)
            Dim tp As TabPage = GetTabPageFromPoint(pt)

            If tp IsNot Nothing Then
                DoDragDrop(tp, DragDropEffects.All)
                RaiseEvent TabOrderChanged() 'Not part of original Control
            End If
        ElseIf e.Button = Windows.Forms.MouseButtons.Right Then
            Dim tab As cTabPage = GetTabPageFromPoint(e.Location)
            If tab IsNot Nothing Then
                Me.SelectedTab = tab
                If tab.TabHeaderContextMenuStrip IsNot Nothing Then tab.TabHeaderContextMenuStrip.Show(Me, New Point(e.Location.X + 2, e.Location.Y + 2))
            End If
        End If
    End Sub

    '// Returns the TabPage whose TabHeader is located at the point 'pt'
    Private Function GetTabPageFromPoint(ByVal pt As Point) As cTabPage
        Dim tp As TabPage = Nothing

        For i As Integer = 0 To Me.TabPages.Count - 1
            If Me.GetTabRect(i).Contains(pt) Then
                tp = Me.TabPages(i)
                Exit For
            End If
        Next

        Return CType(tp, cTabPage)
    End Function

    '// Returns the index of the TabPage
    Private Function FindIndex(ByVal page As cTabPage) As Integer
        Dim rslt As Integer = -1
        For i As Integer = 0 To Me.TabPages.Count - 1
            If Me.TabPages(i) Is CType(page, TabPage) Then
                rslt = i
                Exit For
            End If
        Next
        Return rslt
    End Function
End Class


Public Class cTabControlEventArgs
    Inherits EventArgs

    Private m_TabPage As TabPage = Nothing
    Private m_TabPageIndex As Integer = -1
    Public Cancel As Boolean = False

    Public ReadOnly Property [TabPage]() As TabPage
        Get
            Return m_TabPage
        End Get
    End Property

    Public ReadOnly Property [TabPageIndex]() As Integer
        Get
            Return m_TabPageIndex
        End Get
    End Property

    Public Sub New(ByVal [TabPage] As TabPage, ByVal [TabPageIndex] As Integer)
        m_TabPage = [TabPage]
        m_TabPageIndex = [TabPageIndex]
    End Sub

End Class

Public Delegate Sub cTabControlEventHandler(ByVal sender As Object, ByVal e As cTabControlEventArgs)






